﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using CCAI.Office.PowerPoint.TimedText.Captions;
using TimedText;
using TimedText.Timing;

namespace CCAI.Office.PowerPoint.TimedText
{
    /// <summary>
    /// Accepts a string of XML defining the markers and returns a collection of MediaMarker objects.
    /// </summary>
    /// <remarks>
    /// For more information about Timed Text Authoring Format 1.0, 
    /// see http://www.w3.org/TR/2009/WD-ttaf1-dfxp-20090602/#dfxp-example-subtitle-1
    /// </remarks>
    public class TimedTextMarkerParser : IMarkerParser
    {
        
        public IEnumerable<CaptionRegion> ParseMarkerCollection(XDocument markerXml, TimeSpan timeOffset, TimeSpan defaultEndTime)
        {
            try
            {
                TtElement document = BuildDocument(markerXml, timeOffset, defaultEndTime);
                return BuildRegions(document);
            }
            catch (Exception ex)
            {
                throw new ArgumentException("TimedTextMarkerParser.ParseMarkerCollection received invalid input.", ex);
            }
        }

        public IEnumerable<TimedTextElement> GetTimedTextElementCollection(XDocument markerXml, TimeSpan timeOffset, TimeSpan defaultEndTime)
        {
            try
            {
                TtElement document = BuildDocument(markerXml, timeOffset, defaultEndTime);
                var regions = BuildRegionsCustom(document);

                IList<TimedTextElement> elements = new List<TimedTextElement>();

                foreach (CaptionRegion region in regions)
                {
                    foreach (TimedTextElement child in region.Children)
                    {
                        elements.Add(child);
                    }
                }

                return elements;
            }
            catch (Exception ex)
            {
                throw new ArgumentException("TimedTextMarkerParser.ParseMarkerCollection received invalid input.", ex);
            }
        }

        #region Custom

        private static IEnumerable<CaptionRegion> BuildRegionsCustom(TtElement document)
        {
            var regionElementsHash = document.Head != null
                                         ? document.Head.Children.Where(i => i is LayoutElement)
                                               .SelectMany(i => i.Children)
                                               .Cast<RegionElement>()
                                               .ToDictionary(i => i.Id, i => i)
                                         : new Dictionary<string, RegionElement>();

            var captionRegionsHash = regionElementsHash.Values
                .Select(MapToCaptionRegion)
                .ToDictionary(i => i.Id, i => i);

            if (document.Body != null)
            {
                BuildCaptionsCustom(document.Body, regionElementsHash, captionRegionsHash);
            }

            return captionRegionsHash.Values.ToList();
        }
        private static void BuildCaptionsCustom(TimedTextElementBase timedTextElement,
                                         IDictionary<string, RegionElement> regionElementsHash,
                                         IDictionary<string, CaptionRegion> captionRegionsHash)
        {
            var pElement = timedTextElement as PElement;

            if (pElement != null)
            {
                var regionNameAttribute = pElement.Attributes.FirstOrDefault(i => i.LocalName == "region");
                var regionName = regionNameAttribute != null ? regionNameAttribute.Value : "default region"; //string.Empty;

                RegionElement regionElement;
                if (regionElementsHash.TryGetValue(regionName, out regionElement))
                {
                    var captionElement = MapToCaptionCustom(pElement, regionElement);

                    CaptionRegion captionRegion;
                    if (captionRegionsHash.TryGetValue(regionName, out captionRegion))
                    {
                        captionElement.Index = captionRegion.Children.Count;
                        captionRegion.Children.Add(captionElement);
                    }
                }
            }
            else if (timedTextElement.Children != null)
            {
                timedTextElement.Children
                    .Cast<TimedTextElementBase>()
                    .ForEach(i => BuildCaptions(i, regionElementsHash, captionRegionsHash));
            }
        }

        private static CaptionElement MapToCaptionCustom(PElement pElement, RegionElement region)
        {
            var captionElement = BuildTimedTextElementsCustom(pElement, region);
            captionElement.Id = pElement.Id ?? Guid.NewGuid().ToString();

            return captionElement as CaptionElement;
        }

        private static TimedTextElement BuildTimedTextElementsCustom(TimedTextElementBase element, RegionElement region)
        {
            TimedTextElement timedTextElement = CreateTimedTextElement(element, region);
            var captionElement = (CaptionElement)timedTextElement;

            foreach (TimedTextElementBase c in element.Children)
            {
                TimedTextElement child = BuildTimedTextElements(c, region);

                if (child is TimedTextAnimation)
                {
                    timedTextElement.Animations.Add((TimedTextAnimation)child);
                }
                else if (captionElement != null && child is CaptionElement)
                {
                    captionElement.Children.Add((CaptionElement)child);
                }
            }


            return timedTextElement;
        }

        #endregion



        private static TtElement BuildDocument(XDocument xml, TimeSpan timeOffset, TimeSpan defaultEndTime)
        {
            TtElement parsetree = null;

            try
            {
                parsetree = (TtElement) TimedTextElementBase.Parse(xml.Root);
            }
            catch (TimedTextException)
            {
            }

            if (parsetree == null) throw new TimedTextException("No Parse tree returned");
            if (!parsetree.Valid()) throw new TimedTextException("Document is Well formed XML, but invalid Timed Text");

            var startTime = new TimeCode(timeOffset, TimeExpression.CurrentSmpteFrameRate);
            var endTime = new TimeCode(defaultEndTime, TimeExpression.CurrentSmpteFrameRate);
            parsetree.ComputeTimeIntervals(TimeContainer.Par, startTime, endTime);

            return parsetree;
        }

        private static IEnumerable<CaptionRegion> BuildRegions(TtElement document)
        {
            var regionElementsHash = document.Head != null
                                         ? document.Head.Children.Where(i => i is LayoutElement)
                                               .SelectMany(i => i.Children)
                                               .Cast<RegionElement>()
                                               .ToDictionary(i => i.Id, i => i)
                                         : new Dictionary<string, RegionElement>();

            var captionRegionsHash = regionElementsHash.Values
                .Select(MapToCaptionRegion)
                .ToDictionary(i => i.Id, i => i);

            if (document.Body != null)
            {
                BuildCaptions(document.Body, regionElementsHash, captionRegionsHash);
            }

            return captionRegionsHash.Values.ToList();
        }

        private static void BuildCaptions(TimedTextElementBase timedTextElement,
                                          IDictionary<string, RegionElement> regionElementsHash,
                                          IDictionary<string, CaptionRegion> captionRegionsHash)
        {
            var pElement = timedTextElement as PElement;

            if (pElement != null)
            {
                var regionNameAttribute = pElement.Attributes.FirstOrDefault(i => i.LocalName == "region");
                var regionName = regionNameAttribute != null ? regionNameAttribute.Value : "default region"; //string.Empty;

                RegionElement regionElement;
                if (regionElementsHash.TryGetValue(regionName, out regionElement))
                {
                    var captionElement = MapToCaption(pElement, regionElement);

                    CaptionRegion captionRegion;
                    if (captionRegionsHash.TryGetValue(regionName, out captionRegion))
                    {
                        captionElement.Index = captionRegion.Children.Count;
                        captionRegion.Children.Add(captionElement);
                    }
                }
            }
            else if (timedTextElement.Children != null)
            {
                timedTextElement.Children
                    .Cast<TimedTextElementBase>()
                    .ForEach(i => BuildCaptions(i, regionElementsHash, captionRegionsHash));
            }
        }

        private static CaptionRegion MapToCaptionRegion(RegionElement regionElement)
        {
            var endTime = regionElement.End.TotalSeconds >= TimeSpan.MaxValue.TotalSeconds
                              ? TimeSpan.MaxValue
                              : TimeSpan.FromSeconds(regionElement.End.TotalSeconds);


             var captionRegion = new CaptionRegion
                                    {
                                        Id = regionElement.Id,
                                        Begin = TimeSpan.FromSeconds(regionElement.Begin.TotalSeconds),
                                        End = endTime,
                                        Style = TimedTextStyleParser.MapStyle(regionElement, null)
                                    };

            foreach (TimedTextElementBase element in regionElement.Children)
            {
                TimedTextElement child = BuildTimedTextElements(element, null);
                if (child != null && child.CaptionElementType == TimedTextElementType.Animation)
                {
                    captionRegion.Animations.Add(child as TimedTextAnimation);
                }
            }

            return captionRegion;
        }

        private static CaptionElement MapToCaption(PElement pElement, RegionElement region)
        {
            var captionElement = BuildTimedTextElements(pElement, region);
            captionElement.Id = pElement.Id ?? Guid.NewGuid().ToString();

            return captionElement as CaptionElement;
        }

        private static TimedTextElement BuildTimedTextElements(TimedTextElementBase element, RegionElement region)
        {
            TimedTextElement timedTextElement = CreateTimedTextElement(element, region);
            var captionElement = (CaptionElement) timedTextElement;

            foreach (TimedTextElementBase c in element.Children)
            {
                TimedTextElement child = BuildTimedTextElements(c, region);

                if (child is TimedTextAnimation)
                {
                    timedTextElement.Animations.Add((TimedTextAnimation) child);
                }
                else if (captionElement != null && child is CaptionElement)
                {
                    captionElement.Children.Add((CaptionElement) child);
                }
            }

            return timedTextElement;
        }

        private static TimedTextElement CreateTimedTextElement(TimedTextElementBase element, RegionElement region)
        {
            var captionElement = element is SetElement
                                     ? (TimedTextElement) BuildCaptionAnimationElement(element)
                                     : new CaptionElement();

            captionElement.End = TimeSpan.FromSeconds(element.End.TotalSeconds);
            captionElement.Begin = TimeSpan.FromSeconds(element.Begin.TotalSeconds);

            if (element is BrElement)
            {
                captionElement.CaptionElementType = TimedTextElementType.LineBreak;
            }
            else if (element is AnonymousSpanElement)
            {
                var span = element as AnonymousSpanElement;
                captionElement.CaptionElementType = TimedTextElementType.Text;
                captionElement.Content = span.Text;
                captionElement.Style = TimedTextStyleParser.MapStyle(element, region);
            }
            else if (!(element is SetElement))
            {
                captionElement.CaptionElementType = TimedTextElementType.Container;
                captionElement.Style = TimedTextStyleParser.MapStyle(element, region);
            }

            return captionElement;
        }

        private static TimedTextAnimation BuildCaptionAnimationElement(TimedTextElementBase element)
        {
            var propertyName = element.Attributes
                .Where(i => TimedTextStyleParser.IsValidAnimationPropertyName(i.LocalName))
                .Select(i => i.LocalName)
                .FirstOrDefault();

            return !propertyName.IsNullOrWhiteSpace()
                       ? new TimedTextAnimation
                             {
                                 CaptionElementType = TimedTextElementType.Animation,
                                 PropertyName = propertyName,
                                 Style = TimedTextStyleParser.MapStyle(element, null)
                             }
                       : null;
        }


        //private static string GetRegionName(TimedTextElementBase element)
        //{
        //    string regionName = "";

        //    foreach (TimedTextAttributeBase attr in element.Attributes)
        //    {
        //        if (attr.LocalName == "region")
        //        {
        //            regionName = attr.Value;
        //            break;
        //        }
        //    }

        //    return regionName;
        //}

        //private static void ProcessRegions(IEnumerable<MediaMarker> markers, IDictionary<string, RegionElement> regions)
        //{
        //    var captionRegions = new Dictionary<string, CaptionRegion>();
        //    var unorderedRegions = new List<CaptionRegion>();
        //    foreach (RegionElement region in regions.Values)
        //    {
        //        var captionRegion = new CaptionRegion
        //                                {
        //                                    Id = region.Id,
        //                                    Begin = TimeSpan.FromSeconds(region.Begin.TotalSeconds),
        //                                    End = TimeSpan.FromSeconds(region.End.TotalSeconds),
        //                                    Style = TimedTextStyleParser.MapStyle(region, null)
        //                                };

        //        captionRegion.ZIndex = captionRegion.Style.ZIndex;

        //        foreach (TimedTextElementBase element in region.Children)
        //        {
        //            CaptionElement child = PopulateSpans(element, null);
        //            if (child != null)
        //            {
        //                child.Parent = captionRegion;
        //                if (child.CaptionElementType == CaptionElementType.Animation)
        //                {
        //                    captionRegion.Animations.Add(child as CaptionAnimationElement);
        //                }
        //            }
        //        }

        //        unorderedRegions.Add(captionRegion);
        //    }
        //    unorderedRegions.OrderBy(region => region.ZIndex)
        //                    .ForEach(i => captionRegions.Add(i.Id, i));

        //    foreach (CaptionMediaMarker marker in markers)
        //    {
        //        if (string.IsNullOrWhiteSpace(marker.RegionName))
        //        {
        //            marker.RegionName = "default region";
        //        }
        //        if (captionRegions.ContainsKey(marker.RegionName))
        //        {
        //            marker.Root.Parent = captionRegions[marker.RegionName];
        //        }
        //        marker.Regions = captionRegions;
        //    }
        //}


        //private static IDictionary<string, RegionElement> ExtractRegions(HeadElement head)
        //{
        //    var regionDictionary = new Dictionary<string, RegionElement>();
        //    foreach (TimedTextElementBase child in head.Children)
        //    {
        //        if (child is LayoutElement)
        //        {
        //            foreach (RegionElement regionElement in child.Children)
        //            {
        //                if (!regionDictionary.ContainsKey(regionElement.Id))
        //                {
        //                    regionDictionary.Add(regionElement.Id, regionElement);
        //                }
        //            }
        //        }
        //    }
        //    return regionDictionary;
        //}

        //private static void ProcessPElements(TimedTextElementBase current, List<MediaMarker> markers,
        //                              IDictionary<string, RegionElement> regions, RegionElement currentRegion,
        //                              TimeCode begin, TimeCode end)
        //{
        //    if (currentRegion == null && regions.ContainsKey("default region"))
        //    {
        //        currentRegion = regions["default region"];
        //    }

        //    string regionName = GetRegionName(current);
        //    if (regionName != "" && regions.ContainsKey(regionName))
        //    {
        //        currentRegion = regions[regionName];
        //    }

        //    if (current is PElement)
        //    {
        //        markers.Add(ConvertMarker(current as PElement, currentRegion));
        //    }
        //    else
        //    {
        //        if (current.Children != null)
        //        {
        //            foreach (TimedTextElementBase b in current.Children)
        //            {
        //                ProcessPElements(b, markers, regions, currentRegion, begin, end);
        //            }
        //        }
        //    }
        //}


        //private static MediaMarker ConvertMarker(PElement pElement, RegionElement currentRegion)
        //{
        //    string id = pElement.Id;
        //    TimeCode begin = pElement.Begin;
        //    TimeCode end = pElement.End;

        //    CaptionElement element = PopulateSpans(pElement, currentRegion);

        //    TimeSpan? nEnd = TimeSpan.FromSeconds(end.TotalSeconds);
        //    TimeSpan? nBegin = TimeSpan.FromSeconds(begin.TotalSeconds);
        //    if (id == null)
        //    {
        //        id = Guid.NewGuid().ToString();
        //        //id = style.Id + begin.TotalSeconds.ToString();
        //    }

        //    return BuildCaptionMediaMarker(id, nBegin.Value, nEnd, element, currentRegion);
        //}


        //private static CaptionMediaMarker BuildCaptionMediaMarker(string id, TimeSpan begin, TimeSpan? end, CaptionElement data, RegionElement region)
        //{
        //    return new CaptionMediaMarker
        //               {
        //                   Id = id,
        //                   Begin = begin,
        //                   End = end.HasValue ? end.Value : begin,
        //                   Root = data,
        //                   RegionName = region.Id
        //               };
        //}
    }
}